#include <ESP8266WiFi.h> // 本程序使用 ESP8266WiFi库
#define ISKY_USER_DEFINE 1
#define ISKY_DEBUG_WEBSERVER 0
#include <ESP8266WebServer.h>
#include <FS.h> //闪存文件系统
// #include <FS.h>
#include <ArduinoJson.h> //json数据处理库（第三方）
#include <PubSubClient.h>
#include "SHA256.h"
// #define TARGETIP "192.168.2.182"
// #define TARGETPORT 5001
#define LED2 2

/* 设备的三元组信息*/
#define PRODUCT_KEY "a1g0RdoVbhU"
#define DEVICE_NAME "cam1"
#define DEVICE_SECRET "bf53443b45b408ab3a741b4c77edf232"
// {
//   "ProductKey": "a1g0RdoVbhU",
//   "DeviceName": "cam1",
//   "DeviceSecret": "bf53443b45b408ab3a741b4c77edf232"
// }
/* 线上环境域名和端口号，不需要改 */
#define REGION_ID "cn-shanghai"
#define MQTT_SERVER PRODUCT_KEY ".iot-as-mqtt." REGION_ID ".aliyuncs.com"
#define MQTT_PORT 1883
/* */
#define MQTT_USRNAME DEVICE_NAME "&" PRODUCT_KEY
#define CLIENT_ID "CAM1"
#define CONTENT_STR_FORMAT "clientId" CLIENT_ID "deviceName" DEVICE_NAME "productKey" PRODUCT_KEY "timestamp%d"
#define ALINK_BODY_FORMAT "{\"id\":\"" CLIENT_ID "\",\"version\":\"1.0\",\"method\":\"thing.event.property.post\",\"params\":%s}"
#define ALINK_TOPIC_PROP_POST "/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/event/property/post"

WiFiClient espClient;
PubSubClient client(espClient);

String client_id;
String str1;
char MQTT_PASSWD[64];

static void hex_to_str(char *ptr, char *buf, int len);
void mqttCheckConnect();
void callback(char *topic, byte *payload, unsigned int length);

ESP8266WebServer server(80);      //创建Web服务端口为80
ESP8266WebServer camserver(5001); //创建Web服务端口为5001
void wificonfig();
void removeConfig();
void handlecampost();
// void hNotFound();
void printLocalTime();
const char *ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 8 * 3600;
const int daylightOffset_sec = 0;

long lasttime;
void setup()
{
    pinMode(0, INPUT_PULLUP); //板子上的FLASH按键，我的nodemcu是0
    pinMode(LED2, OUTPUT);
    digitalWrite(LED2, HIGH);
    Serial.begin(115200);
    Serial.println("");
    wificonfig();
    while (WiFi.status() != WL_CONNECTED)
    {
        server.handleClient();
    }
    camserver.begin();
    // camserver.on("/", handlecampost);
    camserver.onNotFound(handlecampost);
    // configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

    delay(100);
    char hashs[32];
    char buff[128];
    int timestamp = millis();
    sprintf(buff, CONTENT_STR_FORMAT, timestamp);
    client_id = String(CLIENT_ID) + "|securemode=3,signmethod=hmacsha256,timestamp=" + String(timestamp) + '|';
    // Serial.println(client_id);
    // Serial.println(CONTENT_STR_FORMAT);
    // Serial.println(ALINK_BODY_FORMAT);

    SHA256 sha256;
    sha256.resetHMAC(DEVICE_SECRET, strlen(DEVICE_SECRET));
    sha256.update(buff, strlen(buff));
    sha256.finalizeHMAC(DEVICE_SECRET, strlen(DEVICE_SECRET), hashs, 32);
    hex_to_str(MQTT_PASSWD, hashs, 32);

    delay(50);

    mqttCheckConnect();
    lasttime = millis();
}

bool data_rec_finish = false;
int OutNum, InNum;
int lastOutNum = 0, lastInNum = 0;
int Content_Len = 0;
int status = 1;

void loop()
{
    // put your main code here, to run repeatedly:
    if (digitalRead(0) == 0)
    { //按键扫描
        removeConfig();
    }
    if (millis() < lasttime)
    {
        lasttime = millis();
    }
    if (millis() - lasttime > 20 * 60 * 1000)
    {
        char param[128];
        char jsonbuf[256];
        sprintf(param, "{\"status\":0}");
        sprintf(jsonbuf, ALINK_BODY_FORMAT, param);
        Serial.println(jsonbuf);
        boolean d = client.publish(ALINK_TOPIC_PROP_POST, jsonbuf);
        if (d)
        {
            data_rec_finish = false;
            Serial.println("publish T and H success");
            client.loop();
        }
        else
        {
            Serial.println("publish T and H fail");
            mqttCheckConnect();
        }
        digitalWrite(LED2, LOW);
        delay(400);
        digitalWrite(LED2, HIGH);
        delay(400);
        digitalWrite(LED2, LOW);
        delay(400);
        digitalWrite(LED2, HIGH);
        lasttime = millis();
    }

    if (data_rec_finish)
    {
        char param[128];
        char jsonbuf[256];
        sprintf(param, "{\"OutNum\":%d,\"InNum\":%d,\"status\":1}", OutNum, InNum);
        sprintf(jsonbuf, ALINK_BODY_FORMAT, param);
        Serial.println(jsonbuf);
        boolean d = client.publish(ALINK_TOPIC_PROP_POST, jsonbuf);
        if (d)
        {
            data_rec_finish = false;
            Serial.println("publish T and H success");
            client.loop();
        }
        else
        {
            Serial.println("publish T and H fail");
            mqttCheckConnect();
        }
        digitalWrite(LED2, LOW);
        delay(400);
        digitalWrite(LED2, HIGH);
        delay(400);
        digitalWrite(LED2, LOW);
        delay(400);
        digitalWrite(LED2, HIGH);
        lasttime = millis();
    }

    camserver.handleClient();
    client.loop(); //检查云端是否有数据下发
    delay(50);
    // status ++;
    // if (status >200)
    // {
    //     status = 0;
    //     Serial.println("loop");
    // }
    // printLocalTime();
}

// void hNotFound()
// {

//     Serial.println(camserver.reqstr);
//     digitalWrite(LED2, HIGH);
//     delay(500);
//     digitalWrite(LED2, LOW);

//     // if (SPIFFS.exists("/index.html"))
//     // {
//     //     // Serial.println("exists");
//     //     File index = SPIFFS.open("/index.html", "r");
//     //     camserver.streamFile(index, "text/html");
//     //     index.close();
//     // }
// }

void handlecampost()
{
    int Conpos = 0;
    int Outpos, Inpos;
    // Serial.println(camserver.reqstr);
    Conpos = camserver.reqstr.indexOf("Content-Length");
    Content_Len = camserver.reqstr.substring(Conpos + 16, Conpos + 19).toInt();
    // Serial.print("Content-Length: ");
    // Serial.println(Content_Len);
    // Serial.println(camserver.reqstr.substring(Conpos + 16, Conpos + 19));
    if (Content_Len < 100)
    {
        OutNum = lastOutNum;
        InNum = lastInNum;
        data_rec_finish = true;
    }
    else
    {
        String temp_str = camserver.reqstr.substring(Conpos + 20);
        // Serial.println(temp_str);
        Outpos = temp_str.indexOf("OutNum");
        Inpos = temp_str.indexOf("InNum");

        InNum = temp_str.substring(Inpos + 8 + 5, Outpos - 10).toInt();
        OutNum = temp_str.substring(Outpos + 8 + 6, Content_Len - 4).toInt();

        // Serial.println(temp_str.substring(Inpos + 8+5, Outpos - 10));
        Serial.println(InNum);
        // Serial.println(temp_str.substring(Outpos + 8+6, Content_Len - 4));
        Serial.println(OutNum);
        lastOutNum = OutNum;
        lastInNum = InNum;
    }
    digitalWrite(LED2, LOW);
    delay(500);
    digitalWrite(LED2, HIGH);
    data_rec_finish = true;
}

void printLocalTime()
{
    // struct tm timeinfo;
    // if (!getLocalTime(&timeinfo))
    // {
    //     Serial.println("Failed to obtain time");
    //     return;
    // }
    // Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
    // Serial.println("###");
}

void handleRoot()
{ //展示网页的关键代码
    // Serial.println("handleroot");
    if (SPIFFS.exists("/index.html"))
    {

        // Serial.println("exists");
        File index = SPIFFS.open("/index.html", "r");
        server.streamFile(index, "text/html");
        index.close();
    }
}

void handleConnect()
{                                     //处理配置信息的函数
    String ssid = server.arg("ssid"); // arg是获取请求参数，视频中最后面展示了请求的完整链接
    String password = server.arg("password");
    server.send(200, "text/plain", "OK");
    WiFi.mode(WIFI_STA);                        //改变wifi模式
    WiFi.begin(ssid.c_str(), password.c_str()); // String类型直接用会报错，不要问为什么，转成char *就行了。
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.println(".");
    }
    Serial.println(WiFi.localIP());
    removeConfig(); // 不管有没有配置先删除一次再说。
    String payload; // 拼接构造一段字符串形式的json数据长{"ssid":"xxxxx","password":"xxxxxxxxxxx"}
    payload += "{\"ssid\":\"";
    payload += ssid;
    payload += "\",\"password\":\"";
    payload += password;
    payload += "\"}";
    File wifiConfig = SPIFFS.open("/config.json", "w");
    wifiConfig.println(payload); //将数据写入config.json文件中
    wifiConfig.close();
}

void removeConfig()
{
    if (SPIFFS.exists("/config.json"))
    { // 判断有没有config.json这个文件
        if (SPIFFS.remove("/config.json"))
        {
            Serial.println("删除旧配置");
        }
        else
        {
            Serial.println("删除旧配置失败");
        }
    }
}
void wificonfig()
{
    if (SPIFFS.begin())
    { // 打开闪存文件系统，记得在你连接板子下载的过程中选Flash Size的时候不要选no SPIFFS,你可以选1M、2M、3M都无所谓，因为两个文件都很小
        Serial.println("闪存文件系统打开成功");
        //    SPIFFS.format();
    }
    else
    {
        Serial.println("闪存文件系统打开失败");
    }
    if (SPIFFS.exists("/config.json"))
    { // 判断有没有config.json这个文件
        Serial.println("存在配置信息，正在自动连接");
        const size_t capacity = JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2) + 60; //分配一个内存空间
        DynamicJsonDocument doc(capacity);                                      // 声明json处理对象
        File configJson = SPIFFS.open("/config.json", "r");
        deserializeJson(doc, configJson); // json数据序列化
        const char *ssid = doc["ssid"];
        const char *password = doc["password"];
        WiFi.mode(WIFI_STA);        // 更换wifi模式
        WiFi.setSleep(false);       //关闭STA模式下wifi休眠，提高响应速度
        WiFi.begin(ssid, password); // 连接wifi
        while (WiFi.status() != WL_CONNECTED)
        {
            delay(500);
            Serial.println(".");
        }
        configJson.close();
        Serial.println(WiFi.localIP());
    }
    else
    {
        Serial.println("不存在配置信息，正在打开web配网模式");
        IPAddress softLocal(192, 168, 1, 1);
        IPAddress softGateway(192, 168, 1, 1);
        IPAddress softSubnet(255, 255, 255, 0);
        WiFi.softAPConfig(softLocal, softGateway, softSubnet);
        WiFi.softAP("esp8266", "12345678"); //这里是配网模式下热点的名字和密码
        Serial.println(WiFi.softAPIP());
    }
    server.on("/", handleRoot);       // web首页监听
    server.on("/set", handleConnect); // 配置ssid密码监听
    server.begin();
}

void mqttCheckConnect()
{
    client.setServer(MQTT_SERVER, MQTT_PORT); /* 连接WiFi之后，连接MQTT服务器 */
    client.setCallback(callback);
    while (!client.connected())
    {
        Serial.println("Connecting to MQTT Server ...");
        // Serial.println(client_id.c_str());
        if (client.connect(client_id.c_str(), MQTT_USRNAME, MQTT_PASSWD))
        {
            Serial.println("MQTT Connected!");
        }
        else
        {
            Serial.println("ERR INFO:");
            Serial.println(client_id);
            Serial.println(MQTT_USRNAME);
            Serial.println(MQTT_PASSWD);
            Serial.print("MQTT Connect err:");
            Serial.println(client.state());
            delay(5000);
        }
    }
}

static void hex_to_str(char *ptr, char *buf, int len)
{
    for (int i = 0; i < len; i++)
    {
        sprintf(ptr, "%02x", buf[i]);
        ptr += 2;
    }
}

void callback(char *topic, byte *payload, unsigned int length)
{
    Serial.print("Message arrived [");
    Serial.print(topic);
    Serial.print("] ");

    payload[length] = '\0';
    Serial.println((char *)payload);
}